<?php

// vim: expandtab shiftwidth=2 softtabstop=2 tabstop=2

/**
 * Provide initialization class
 *
 * PHP version 5
 *
 * @category  Classes
 * @package   mPack
 * @author    Marcin Sztolcman <marcin /at/ urzenia /dot/ net>
 * @copyright 2006 Marcin Sztolcman
 * @license   GPL v.2
 * @version   SVN: $Id: class_minit.php 29 2008-01-20 23:15:20Z urzenia $
 * @link      http://m-pack.googlecode.com
 */

/**
 * Initialization class
 *
 * Sets compressing (via zlib extension) output of scripts, converts encoding
 * of output from character set in files to defined encoding (ex. files like
 * code or templates can be in ISO-8859-2, but output will be in UTF-8).
 *
 * Cannot be superclass for any class (final).
 *
 * Have to be instantiated like:
 * try {
 *   new mInit('iso-8859-2', 'utf-8', 5);
 * } catch (Exception $e) {
 *   echo $e->getMessage();
 *   exit;
 * }
 *
 * Error codes:
 * 200 RuntimeException 'Compressing failed: "zlib" isn't loaded.
 * 201 RuntimeException Encoding to "%s" failed: neither "iconv" and "mbstring" isn't loaded.
 *
 * @category  Classes
 * @package   mPack
 * @author    Marcin Sztolcman <marcin /at/ urzenia /dot/ net>
 * @copyright 2006 Marcin Sztolcman
 * @license   GPL v.2
 * @version   SVN: $Id: class_minit.php 29 2008-01-20 23:15:20Z urzenia $
 * @link      http://m-pack.googlecode.com
 */
final class mInit
{
  /**
   * Constant - default source encoding
   */
  const ENC_FROM    = 'iso-8859-2';

  /**
   * Constant - default target encoding
   */
  const ENC_TO      = 'UTF-8';

  /**
   * Constant - default compression level
   */
  const COMP_LEVEL  = 5;

  /**
   * Class initialized already?
   *
   * @var boolean
   * @access private
   * @static
   */
  private static $_initInitialized = false;

  /**
   * Holds status of output buffering.
   *
   * @var boolean
   * @access private
   */
  private $_obInitialized = false;

  /**
   * Map error levels to their names.
   *
   * @var array
   * @access private
   * @static
   */
  private static $_errtype = array (
    E_ERROR           => 'Error',
    E_WARNING         => 'Warning',
    E_PARSE           => 'Parsing Error',
    E_NOTICE          => 'Notice',
    E_CORE_ERROR      => 'Core Error',
    E_CORE_WARNING    => 'Core Warning',
    E_COMPILE_ERROR   => 'Compile Error',
    E_COMPILE_WARNING => 'Compile Warning',
    E_USER_ERROR      => 'User Error',
    E_USER_WARNING    => 'User Warning',
    E_USER_NOTICE     => 'User Notice',
    E_STRICT          => 'Runtime Notice'
  );

  /**
   * Include path
   *
   * @var array
   * @access private
   */
  private $_incPath       = array();

  /**
   * Enable/disable debug mode
   *
   * @var boolean
   * @access private
   */
  private $_debug         = false;

  /**
   * Email address where diagnostic emails have to be sent
   *
   * @var string
   * @access private
   */
  private $_email         = null;

  /**
   * Where log have to ba saved
   *
   * @var string
   * @access private
   */
  private $_logPath       = './php.log';

  /**
   * Set compressing level.
   *
   * @param integer $comp_level
   *
   * @return string  name of compressing function name (for ob_start())
   * @throws Exception if zlib not loaded
   *
   * @access private
   */
  private function _initCompress($comp_level)
  {
    if (!extension_loaded('zlib')) {
      throw new RuntimeException('Compressing failed: "zlib" isn\'t loaded.', 200);
    }

    ini_set('zlib.compresss_level', $comp_level);
    return 'ob_gzhandler';
  }

  /**
   * Set encoding method.
   *
   * Checks for existing of proper extension: iconv or mbstring. If any
   * of them is loaded, proper handler for ob_start() is returned, either
   * false.
   *
   * @param string $enc_from character set of files (present)
   * @param string $enc_to   output character set
   *
   * @return string  name of handler function to converts character set
   * @throws Exception if none of valid extensions loaded
   *
   * @access private
   */
  private function _initEncoding($enc_from, $enc_to)
  {
    if (extension_loaded('iconv')) {
      iconv_set_encoding('internal_encoding', $enc_from);
      iconv_set_encoding('output_encoding', $enc_to);
      return 'ob_iconv_handler';
    } elseif (extension_loaded('mbstring')) {
      mb_internal_encoding($enc_from);
      mb_http_output($enc_to);
      return 'mb_output_handler';
    } else {
      throw new RuntimeException(sprintf('Encoding to "%s" failed: neither' .
         ' "iconv" and "mbstring" isn\'t loaded.', $enc_to), 201);
    }
  }

  /**
   * Set default options for php settings
   *
   * Set also error handler.
   *
   * @return null
   *
   * @access private
   */
  private function _initPhpSettings() {
    //These settings cannot be set from script, it must be in PHP config
    //or .htaccess (if allowed):
    // ini_set('arg_separator.output',         '&amp;');
    // ini_set('magic_quotes_gpc',             0);
    // ini_set('register_globals',             0);
    //
    // php_value arg_separator.output '&amp;'
    // php_flag magic_quotes_gpc 0
    // php_flag register_globals 0

    ini_set('include_path',
      implode(PATH_SEPARATOR,
        array_merge(
          explode(PATH_SEPARATOR, get_include_path()),
          $this->_incPath
        )
      )
    );
    ini_set('arg_separator.input',          ';&');
    ini_set('magic_quotes_runtime',         0);
    ini_set('magic_quotes_sybase',          0);
    ini_set('zend.ze1_compatibility_mode',  0);
    ini_set('variables_order',              'EGPCS');
    ini_set('sendmail_from',                $this->_email);
    ini_set('session.hash_function',        1);
    ini_set('session.auto_start',           1);
    ini_set('register_long_arrays',         0);

    if ($this->_debug) {
      ini_set('error_reporting',            E_STRICT | E_ALL);
      ini_set('display_errors',             1);
      ini_set('mysql.trace_mode',           1);
      ini_set('error_log',                  null);
      ini_set('log_errors',                 0);

      ini_set('html_errors',                1);
      ini_set('error_prepend_string',       '<p style="color: #ff0000">');
      ini_set('error_append_string',        '</p>');
      ini_set('track_errors',               1);
    } else {
      ini_set('error_reporting',            E_ERROR | E_STRICT |
                                            E_USER_ERROR | E_USER_WARNING |
                                            E_USER_NOTICE);
      ini_set('display_errors',             0);
      ini_set('mysql.trace_mode',           0);
      ini_set('error_log',                  $this->_logPath);
      ini_set('log_errors',                 1);
      ini_set('track_errors',               0);

      set_error_handler(array($this, 'error_handler'));
      set_exception_handler(array($this, 'error_handler'));
    }
  }


  /**
   * Constructor
   *
   * Checks for valid parameters, and initializes proper options.
   *
   * @param string  $enc_from character set of files (present)
   * @param string  $enc_to output character set
   * @param integer $comp_level
   * @param array   $email
   *
   * @access public
   */
  public function __construct($enc_from=null, $enc_to=null, $comp_level=null, $email=null)
  {
    if (self::$_initInitialized) {
      return;
    }
    self::$_initInitialized = true;



    if (!is_null($email)) {
      $this->_email = $email;
    }

    if (is_null($comp_level)) {
      $comp_level = self::COMP_LEVEL;
    }
    if (is_null($enc_from)) {
      $enc_from = self::ENC_FROM;
    }
    if (is_null($enc_to)) {
      $enc_to = self::ENC_TO;
    }

    if (defined('DEBUG') && DEBUG) {
      $this->_debug = true;
    }

    $ob_start_opts = array();

    if ($comp_level > 0) {
      $comp = $this->_initCompress($comp_level);
      if ($comp !== false) {
        $ob_start_opts[] = $comp;
      }
    }
    if ($enc_from !== $enc_to) {
      $enc = $this->_initEncoding($enc_from, $enc_to);
      if ($enc !== false) {
        $ob_start_opts[] = $enc;
      }
    }

    if (count($ob_start_opts) > 0) {
      ob_start($ob_start_opts);
      $this->_obInitialized = true;
    } else {
      $this->_obInitialized = false;
    }

    $this->_stripslashes();
    $this->_initPhpSettings();
  }

  /**
   * Destructor
   *
   * If ob_start() was called, it will call ob_flush().
   *
   * @access public
   */
  public function __destruct()
  {
    if ($this->_obInitialized) {
      ob_flush();
    }
  }

  /**
   * Error handler
   *
   * These method is set as error/exception handler for Core CMS.
   * If something is wrong, and debug mode is off, error messages
   * are logged into a file, and no error info is on main page.
   * Additionally, if error type is E_STRICT, E_ERROR or E_USER_ERROR,
   * an email is sending to admin (if his address is set).
   *
   * WARNING: Fatal errors aren't handled by this function - PHP
   * doesn't allow this.
   *
   * @param mixed $errno   int if we are handling errors, object if exceptions
   * @param mixed $errmsg  int for errors, otherwise null
   * @param mixed $errfile string for errors, otherwise null
   * @param mixed $errline int for errors, otherwise null
   * @param mixed $vars  all available variables in error context
   *
   * @access public
   */
  public function error_handler($errno, $errmsg=null, $errfile=null,
                  $errline=null, $vars=null)
  {
    if (is_object($errno)) {  // if we handle an exception
      $errmsg  = sprintf('Uncaught exception (%s): %s',
        get_class($errno),
        $errno->getMessage()
      );
      $errfile = $errno->getFile();
      $errline = $errno->getLine();
      $errcode = $errno->getCode();
    } else {          // if we handle error
      $errcode = self::$_errtype[$errno];
    }

    // Error messages have links to reference manual. We won't them.
    $errmsg = preg_replace('# \[<a href=.*?</a>\]#', '', $errmsg);

    $msg1 = array(
      'Error:',
      'Type: ' . $errcode,
      'File: ' . $errfile,
      'Line: ' . $errline,
      'Error message: ' . $errmsg
    );
    $msg1 = implode("\n\t", $msg1); //version for logs
    $msg2 = implode("\n\t", array(  //version for email
      $msg1, '', '',
      'All variables:', '',
      print_r($vars, 1))
    );

    // in_array() can be quicker, but switch() is more extendable - in
    // future we may want to handle additional actions for any type of
    // error
    switch ($errno) {
      case E_STRICT:
      case E_ERROR:
      case E_USER_ERROR:
        if ($this->_email) {
          @mail($this->_email, 'Error: ' . $errcode, $msg2);
        }
        break;
    }

    error_log($msg1, 0); // we want to log into specified file
                         // (see: mInit::_initPhpSettings(())
  }

  /**
   * Automatically strip slashes from superglobals if needed.
   *
   * @return null
   *
   * @access private
   */
  private function _stripslashes() {
    if (get_magic_quotes_gpc()) {
      array_walk_recursive($_POST,    'stripslashes');
      array_walk_recursive($_GET,     'stripslashes');
      array_walk_recursive($_COOKIE,  'stripslashes');

      if (isset($_FILES) && is_array($_FILES)) {
        foreach ($_FILES as &$file) {
          if (is_array($file) && isset($file['name'])) {
            $file['name'] = stripslashes($file['name']);
        }
      }
    }
}

?>
